//
// GPSMapEdit
// (c) Konstantin Galichsky (kg@geopainting.com), 2007
//
// Routing tool.
//

# include "StdAfx.h"
# include "Resource.h"
# include "Tool.h"
# include "RoutingGraph.h"
# include "Map.h"
# include "Selection.h"
# include "MapView.h"
# include "Scale.h"
# include "Position.h"
# include "PtAlgo.h"
# include "DrawHelpers.h"
# include "Menu.h"
# include "Options.h"
# include "Load.h"
# include "Save.h"
# include "Dialog.h"
# include "Button.h"
# include "ComboBox.h"
# include "Settings.h"
# include "Globals.h"

static map_t g_mapRouting;
static rgn_t * g_pRoute;

static
rgn_t & _CreateWpt (const point_t & _pt) {
	rgn_t & wpt = g_mapRouting.CreateRgn (rkPoint);
	wpt.SetWaypointType ();
	wpt.pFileRef  = NULL;
	wpt.cBeginPos = -1;
	wpt.cEndPos   = -1;
	wpt.cFileLine = -1;
	wpt.strLabel  = "w";

	wpt.origins.push_back (rgn_origin_t ());
	rgn_origin_t & origin = wpt.origins.back ();
	origin.cLevel = 0;
	origin.point  = _pt;

	waypoint_info_t * const pWptExtra = wpt.pExtra->wtr.CreateWaypoint ();
	pWptExtra->wGarminType = 0x12;
	pWptExtra->SetShow (waypoint_info_t::showSymbol);
	pWptExtra->wFlags |= route_info_t::defHideLabel;

	wpt.RefreshAttributes (g_mapRouting, false);

	return wpt;
}

static
const point_t & _GetPoint (const rgn_t * _pWpt) {
	assert (_pWpt->IsWaypoint ());
	assert (! _pWpt->origins.empty ());
	return _pWpt->origins [0].point;
}

static const COLORREF c_colRoutes [] = {c_colGreen, c_colBlue, c_colRed};

static
rgn_t & _CreateRoute (const point_t & _pointFirst, size_t _cColor) {
	rgn_t & route = g_mapRouting.CreateRgn (rkPolyline);
	route.SetRouteType ();
	route.strLabel = "r";

	route.elements.push_back (rgn_element_t ());
	rgn_element_t & element = route.elements [0];
	element.cLevel = 0;
	element.points.push_back (_pointFirst);

	route_info_t * const pRouteExtra = route.pExtra->wtr.CreateRoute ();
	pRouteExtra->wFlags |= route_info_t::defHideLabel;
//	pRouteExtra->wFlags |= route_info_t::defAutoName;
	if (_cColor != -1) {
		pRouteExtra->wFlags |= route_info_t::defLineColor;
		pRouteExtra->colLine = c_colRoutes [_cColor % ARRAY_SIZE (c_colRoutes)];
	}

	pRouteExtra->points.resize (1);
	pRouteExtra->points [0].pWaypoint = & _CreateWpt (_pointFirst);

	route.RefreshAttributes (g_mapRouting, false);

	return route;
}

static
COLORREF _GetRouteColor (rgn_t * _pRoute) {
	if (_pRoute && _pRoute->pExtra) {
		const route_info_t * const pRouteExtra = _pRoute->pExtra->wtr.route ();
		if (pRouteExtra->wFlags & route_info_t::defLineColor)
			return pRouteExtra->colLine;
	}
	return GetDefaultRouteColor ();
}

static
size_t _GetRoutePoints (rgn_t * _pRoute) {
	if (_pRoute == NULL)
		return 0;
	if (_pRoute->elements.empty ())
		return NULL;
	return _pRoute->elements [0].points.size ();
}

static
bool _GetRouteLastPoint (rgn_t * _pRoute, point_t & _pointLast) {
	if (_pRoute == NULL)
		return false;

	if (_pRoute->elements.empty ())
		return false;

	const size_t cPoints = _pRoute->elements [0].points.size ();
	if (cPoints == 0)
		return false;

	_pointLast = _pRoute->elements [0].points [cPoints - 1];
	return true;
}

static
rgn_t * _GetRouteLastWpt (rgn_t * _pRoute) {
	CCritSec::CLock lock (g_cs);

	if (_pRoute == NULL)
		return NULL;

	route_info_t * const pRouteExtra = _pRoute->pExtra->wtr.route ();
	if (pRouteExtra == NULL)
		return NULL;

	const size_t cPoints = pRouteExtra->points.size ();
	if (cPoints == 0)
		return false;

	return pRouteExtra->points [cPoints - 1].pWaypoint;
}

static
bool _RouteRemoveLastLeg (rgn_t * _pRoute) {
	if (_pRoute == NULL)
		return false;

	CCritSec::CLock lock (g_cs);

	if (_pRoute->elements.empty ())
		return false;
	points_t & points = _pRoute->elements [0].points;

	route_info_t * const pRouteExtra = _pRoute->pExtra->wtr.route ();
	if (pRouteExtra == NULL)
		return false;
	routepoints_t & rpoints = pRouteExtra->points;

	const size_t cPoints = points.size ();

	rgn_t * const pLastWpt = cPoints >= 1 ? rpoints [cPoints - 1].pWaypoint : NULL;
	if (pLastWpt)
		pLastWpt->Delete (g_mapRouting);

	if (cPoints <= 1) {
		_pRoute->Delete (g_mapRouting);
		return true;
	}

	int nLastButOne;
	for (nLastButOne = cPoints - 2; nLastButOne >= 0; -- nLastButOne) {
		if (rpoints [nLastButOne].pWaypoint != NULL)
			break;
	}

	if (nLastButOne >= 0) {
		points.resize (nLastButOne + 1);
		rpoints.resize (nLastButOne + 1);
	}

	return false;
}

static
CRoutingGraphBase * _BuildRoutingGraph () {
	BuildRoute_t br;
	br.pMap               = & g_map;
	br.Vehicle            = (Vehicle_t)        GetSetting ("Routing_Vehicle", (DWORD) vehCar);
	br.Mode               = (BuildRouteMode_t) GetSetting ("Routing_Mode",    (DWORD) brmShortest);
	br.bAvoidTollRoads    = GetSetting ("Routing_AvoidTollRoads",    false);
	br.bAvoidUnpavedRoads = GetSetting ("Routing_AvoidUnpavedRoads", false); 

	return BuildRoutingGraph (br);
}

typedef std::vector<rgn_t *> waypoints_t;

static
void _BuildRoute (
	CRoutingGraphBase * _pRG,
	const waypoints_t & _pointsVia, rgn_t & _route, bool _bAppend,
	size_t _cProgressFrom, size_t _cProgressTo
) {
	const size_t cViaPoints = _pointsVia.size ();
	if (cViaPoints < 2)
		return;

	CCritSec::CLock lock (g_cs);

	points_t route;

	size_t * const pcViaPoints = (size_t *) ::alloca (sizeof (size_t)*cViaPoints);
	size_t cViaPoint;
	for (cViaPoint = 0; cViaPoint + 1 < cViaPoints; ++ cViaPoint) {
		const size_t cProgressFrom = _cProgressFrom + cViaPoint      *(_cProgressTo - _cProgressFrom)/(cViaPoints - 1);
		const size_t cProgressTo   = _cProgressFrom + (cViaPoint + 1)*(_cProgressTo - _cProgressFrom)/(cViaPoints - 1);
		points_t route_leg;
		_pRG->BuildRoute (_GetPoint (_pointsVia [cViaPoint]), _GetPoint (_pointsVia [cViaPoint + 1]), route_leg, cProgressFrom, cProgressTo);

		route.insert (route.end (), route_leg.begin (), route_leg.end ());

		pcViaPoints [cViaPoint + 1] = route.size () - 1;
	}
	pcViaPoints [0] = 0;

	rgn_element_t & element = _route.elements [0];
	element.cLevel = 0;

	const size_t cOldRoutePoints = _bAppend ? element.points.size () : 0;
	if (_bAppend)
		element.points.insert (element.points.end (), route.begin () + 1, route.end ());
	else
		element.points.swap (route);
	const size_t cNewRoutePoints = element.points.size ();

	route_info_t * const pRouteExtra = _route.pExtra->wtr.route ();
	routepoints_t & RoutePoints = pRouteExtra->points;
	RoutePoints.resize (cNewRoutePoints);
	for (size_t c = cOldRoutePoints; c < cNewRoutePoints; ++ c)
		RoutePoints [c].pWaypoint = NULL;

	// Mark via points.
	const size_t cViaPoint0 = _bAppend ? 1 : 0;
	for (cViaPoint = cViaPoint0; cViaPoint < cViaPoints; ++ cViaPoint) {
		assert (cOldRoutePoints + pcViaPoints [cViaPoint] - cViaPoint0 < cNewRoutePoints);
		RoutePoints [cOldRoutePoints + pcViaPoints [cViaPoint] - cViaPoint0].pWaypoint = _pointsVia [cViaPoint];
	}

	_route.RefreshAttributes (g_mapRouting, false);
}

static
void _BuildRouteLeg (rgn_t & _route, const point_t & _pointLast) {
	CCritSec::CLock lock (g_cs);

	CRoutingGraphBase * const pRG = _BuildRoutingGraph ();
	if (! pRG)
		return;
	CObjectHolder<CRoutingGraphBase> pRG_Holder (pRG);

	waypoints_t pointsVia (2);
	pointsVia [0] = _GetRouteLastWpt (& _route);
	pointsVia [1] = & _CreateWpt (_pointLast);
	_BuildRoute (pRG, pointsVia, _route, true, 0, 100);
}

static
void _RebuildRoutes () {
	{
		CCritSec::CLock lock (g_cs);

		CRoutingGraphBase * const pRG = _BuildRoutingGraph ();
		if (! pRG)
			return;
		CObjectHolder<CRoutingGraphBase> pRG_Holder (pRG);

		rgns_t & routes = g_mapRouting.layers [rkPolyline].rgns;
		const size_t cRoutes = g_mapRouting.layers [rkPolyline].cRgns;
		size_t cRoute = 0;
		for (rgns_t::iterator iRgn = routes.begin (); iRgn != routes.end (); ++ iRgn, ++ cRoute) {
			rgn_t & rgn = * iRgn;
			if (rgn.IsDeleted () || ! rgn.IsRoute ())
				continue;

			if (rgn.elements.empty ())
				continue;
			const points_t & points = rgn.elements [0].points;
			const routepoints_t & RoutePoints = rgn.pExtra->wtr.route ()->points;
			const size_t cPoints = __min (points.size (), RoutePoints.size ());

			waypoints_t pointsVia;
			for (size_t c = 0; c < cPoints; ++ c) {
				if (RoutePoints [c].pWaypoint != NULL)
					pointsVia.push_back (RoutePoints [c].pWaypoint);
			}

			const size_t cProgressFrom = cRoute      *100/cRoutes;
			const size_t cProgressTo   = (cRoute + 1)*100/cRoutes;
			_BuildRoute (pRG, pointsVia, rgn, false, cProgressFrom, cProgressTo);
		}
	}

	Refresh ();
}

static
void _RebuildRoute (rgn_t & _route) {
	CCritSec::CLock lock (g_cs);

	CRoutingGraphBase * const pRG = _BuildRoutingGraph ();
	if (! pRG)
		return;
	CObjectHolder<CRoutingGraphBase> pRG_Holder (pRG);

	if (_route.elements.empty ())
		return;
	const points_t & points = _route.elements [0].points;
	const routepoints_t & RoutePoints = _route.pExtra->wtr.route ()->points;
	const size_t cPoints = __min (points.size (), RoutePoints.size ());

	waypoints_t pointsVia;
	for (size_t c = 0; c < cPoints; ++ c) {
		if (RoutePoints [c].pWaypoint != NULL)
			pointsVia.push_back (RoutePoints [c].pWaypoint);
	}

	_BuildRoute (pRG, pointsVia, _route, false, 0, 10);
}

static
void _ReverseRoute (rgn_t & _route) {
	CCritSec::CLock lock (g_cs);

	if (_route.elements.empty ())
		return;
	points_t & points = _route.elements [0].points;
	routepoints_t & RoutePoints = _route.pExtra->wtr.route ()->points;

	std::reverse (points.begin (), points.end ());
	std::reverse (RoutePoints.begin (), RoutePoints.end ());

	_RebuildRoute (_route);
}

static
bool _CollectGarbage () {
	bool bRefresh = false;

	CCritSec::CLock lock (g_cs);

	// Prepare waypoints.
	rgns_t & wpts = g_mapRouting.layers [rkPoint].rgns;
	rgns_t::iterator iRgn;
	for (iRgn = wpts.begin (); iRgn != wpts.end (); ++ iRgn) {
		rgn_t & rgn = * iRgn;
		if (rgn.IsDeleted () || ! rgn.IsWaypoint ())
			continue;

		waypoint_info_t * const pExtra = rgn.pExtra ? rgn.pExtra->wtr.wpt () : NULL;
		if (pExtra)
			pExtra->wFlags &= ~waypoint_info_t::defTmpBit;
	}

	// Remove tracks and hide labels of routes.
	rgns_t & routes = g_mapRouting.layers [rkPolyline].rgns;
	for (iRgn = routes.begin (); iRgn != routes.end (); ++ iRgn) {
		rgn_t & rgn = * iRgn;
		if (rgn.IsDeleted ())
			continue;

		if (rgn.IsRoute ()) {
			route_info_t * const pExtra = rgn.pExtra ? rgn.pExtra->wtr.route () : NULL;
			if (pExtra) {
				if ((pExtra->wFlags & route_info_t::defHideLabel) == 0) {
					pExtra->wFlags |= route_info_t::defHideLabel;
					bRefresh = true;
				}

				// Mark used waypoints.
				routepoints_t & points = pExtra->points;
				const size_t cPoints = points.size ();
				for (size_t c = 0; c < cPoints; ++ c) {
					rgn_t * const pWpt = points [c].pWaypoint;
					if (pWpt) {
						waypoint_info_t * const pWptExtra = pWpt->pExtra ? pWpt->pExtra->wtr.wpt () : NULL;
						if (pWptExtra)
							pWptExtra->wFlags |= waypoint_info_t::defTmpBit;
					}
				}
			}
		} else {
			rgn.Delete (g_mapRouting);
			bRefresh = true;
		}
	}

	// Remove solitary waypoints and labels of waypoints.
	for (iRgn = wpts.begin (); iRgn != wpts.end (); ++ iRgn) {
		rgn_t & rgn = * iRgn;
		if (rgn.IsDeleted ())
			continue;

		if (rgn.IsWaypoint ()) {
			waypoint_info_t * const pExtra = rgn.pExtra ? rgn.pExtra->wtr.wpt () : NULL;
			if (pExtra) {
				if (pExtra->GetShow () != waypoint_info_t::showSymbol) {
					pExtra->SetShow (waypoint_info_t::showSymbol);
					bRefresh = true;
				}

				if ((pExtra->wFlags & waypoint_info_t::defTmpBit) == 0) {
					// NOTE: This waypoint is not used in any route.
					rgn.Delete (g_mapRouting);
					bRefresh = true;
				}
			}
		} else {
			rgn.Delete (g_mapRouting);
			bRefresh = true;
		}
	}

	return bRefresh;
}

////////////////////////////////////////////////////////////////

# pragma const_seg (".text")

static const UINT c_uiModeRBtns [] = {IDC_SHORTEST, IDC_FASTEST};

static
const CComboBoxData g_cbdVehicle [] = {
	{IDS_VEHICLE_PEDESTRIAN, vehPedestrian},
	{IDS_VEHICLE_BICYCLE,    vehBicycle},
	{IDS_VEHICLE_CAR,        vehCar},
	{IDS_VEHICLE_TAXI,       vehTaxi},
	{IDS_VEHICLE_BUS,        vehBus},
	{IDS_VEHICLE_EMERGENCY,  vehEmergency},
	{IDS_VEHICLE_DELIVERY,   vehDelivery},
	{IDS_VEHICLE_TRUCK,      vehTruck},
};
const size_t c_cVehicles = sizeof (g_cbdVehicle)/sizeof (g_cbdVehicle [0]);

# pragma const_seg ()

class CRoutingDlg : public CDialog {
  public:
	CRoutingDlg () : CDialog (IDD_ROUTING) {}

	// Override.
	virtual bool OnInit (HWND _hwndFocus) {
		// Load & Save.
		SetButtonIcon (m_hWnd, IDC_LOAD, IDI_LOAD);
		SetButtonIcon (m_hWnd, IDC_SAVE, IDI_SAVE);
		SetButtonIcon (m_hWnd, IDC_LOCATE, IDI_LOCATE);

		// Enable multiple routes.
		CheckDlgButton (IDC_ENABLE_MULTIPLE, GetSetting ("EnableMultipleRoutes", false));

		// Mode.
		const bool bShortest = GetSetting ("Routing_Mode", (DWORD) brmShortest) == brmShortest;
		CheckRadioButton (bShortest ? IDC_SHORTEST : IDC_FASTEST, c_uiModeRBtns, ARRAY_SIZE (c_uiModeRBtns));

		// Vehicle.
		FillComboBox (m_hWnd, IDC_VEHICLE, g_cbdVehicle, c_cVehicles, GetSetting ("Routing_Vehicle", (DWORD) vehCar));

		// Avoid toll roads.
		CheckDlgButton (IDC_AVOID_TOLL, GetSetting ("Routing_AvoidTollRoads", false));

		// Avoid unpaved roads.
		CheckDlgButton (IDC_AVOID_UNPAVED, GetSetting ("Routing_AvoidUnpavedRoads", false));

		EnableButtons ();

		return true;
	}

	virtual void OnCommand (WORD _wCmd, WORD _wNotifyCode, HWND _hwndCtl) {
		if (_wCmd != IDC_VEHICLE)
			_RestoreFocus ();

		switch (_wCmd) {
			case IDC_LOAD:        _LoadRoutes (); return;
			case IDC_SAVE:        _SaveRoutes (); return;
			case IDC_LOCATE:      _LocateRoutes (); return;
			case IDC_DELETE_ALL:  DeleteRoutes (); return;
			case IDC_REBUILD_ALL: _RebuildRoutes (); return;

			case IDC_ENABLE_MULTIPLE:
				SaveSetting ("EnableMultipleRoutes", IsDlgButtonChecked (IDC_ENABLE_MULTIPLE));
				return;

			case IDC_SHORTEST:
				CheckRadioButton (IDC_SHORTEST, c_uiModeRBtns, ARRAY_SIZE (c_uiModeRBtns));
				SaveSetting ("Routing_Mode", (DWORD) brmShortest);
				break;
			case IDC_FASTEST:
				CheckRadioButton (IDC_FASTEST, c_uiModeRBtns, ARRAY_SIZE (c_uiModeRBtns));
				SaveSetting ("Routing_Mode", (DWORD) brmFastest);
				break;

			case IDC_VEHICLE:
				if (_wNotifyCode == CBN_SELCHANGE) {
					int nVehicle = 0;
					if (GetComboBoxItemData (m_hWnd, IDC_VEHICLE, nVehicle))
						SaveSetting ("Routing_Vehicle", (DWORD) nVehicle);

					_RestoreFocus ();
				}
				break;

			case IDC_AVOID_TOLL:
				SaveSetting ("Routing_AvoidTollRoads", IsDlgButtonChecked (IDC_AVOID_TOLL));
				break;
			case IDC_AVOID_UNPAVED:
				SaveSetting ("Routing_AvoidUnpavedRoads", IsDlgButtonChecked (IDC_AVOID_UNPAVED));
				break;
		}
	}

	virtual void OnMove (int _x, int _y) {
		RECT rect;
		::GetWindowRect (m_hWnd, & rect);
		POINT p = {rect.left, rect.top};
		::ScreenToClient (g_hMapWnd, & p);
		if (p.x < 0)
			p.x = 0;
		if (p.y < 0)
			p.y = 0;

		SaveSetting ("RoutingX", DWORD (p.x));
		SaveSetting ("RoutingY", DWORD (p.y));
	}

	virtual void OnClose () {
		CTool::SetCurrentTool (toolDefault);

		_RestoreFocus ();
	}

	void DeleteRoutes ();

	void EnableButtons ();

  private:
	// Restore focus to keep keyboard input (accelerators) in main window.
	void _RestoreFocus ();

	void _LoadRoutes ();
	void _SaveRoutes ();
	void _LocateRoutes ();
};

//////////////////////////////////////////////////////////////

void CRoutingDlg::_RestoreFocus () {
	::SetFocus (NULL);
	::SetFocus (g_hWnd);
}

void CRoutingDlg::EnableButtons () {
	bool bEmptyRoutes, bEmptyMap;
	{
		CCritSec::CLock lock (g_cs);

		bEmptyMap = g_map.empty ();
		bEmptyRoutes = g_mapRouting.empty ();
	}

	EnableDlgItem (IDC_LOAD,        ! bEmptyMap);
	EnableDlgItem (IDC_SAVE,        ! bEmptyRoutes && ! bEmptyMap);
	EnableDlgItem (IDC_LOCATE,      ! bEmptyRoutes && ! bEmptyMap);
	EnableDlgItem (IDC_REBUILD_ALL, ! bEmptyRoutes && ! bEmptyMap);
	EnableDlgItem (IDC_DELETE_ALL,  ! bEmptyRoutes && ! bEmptyMap);
}

const char c_strDefaultName [] = "AutoRouting.gdb";

void CRoutingDlg::_LoadRoutes () {
	CFileTypeInfo::ShowOpenFile (false, true, g_mapRouting, ftiRoutes, c_strDefaultName);

	const bool bRefresh = _CollectGarbage ();

	EnableButtons ();

	if (bRefresh)
		Refresh ();
}

void CRoutingDlg::_SaveRoutes () {
	CFileTypeInfo * const pGdbSaverInfo = CFileTypeInfo::FindSaverInfoByClass ("garmin-gdb");
	assert (pGdbSaverInfo);
	if (pGdbSaverInfo == NULL)
		return;
	const std::auto_ptr<CMapSaver> pSaver = std::auto_ptr<CMapSaver> (pGdbSaverInfo->m_pfCreateSaver ());

	CCritSec::CLock lock (g_cs);
	pSaver->PrepareToSave (g_mapRouting);
	pSaver->Save (NULL, c_strDefaultName, g_mapRouting);
}

void CRoutingDlg::DeleteRoutes () {
	{
		CCritSec::CLock lock (g_cs);
		g_mapRouting.clear ();
	}

	g_pRoute = NULL;

	EnableButtons ();

	Refresh ();
}

void CRoutingDlg::_LocateRoutes () {
	// Update bounding rectangle.
	g_mapRouting.rectBound = rect_t ();
	for (size_t cKind = 0; cKind < rkMax; ++ cKind) {
		const layer_t & layer = g_mapRouting.layers [cKind];
		const rgns_t & rgns = layer.rgns;
		for (rgns_t::const_iterator iRgn = rgns.begin (); iRgn != rgns.end (); ++ iRgn) {
			const rgn_t & rgn = * iRgn;
			if (rgn.IsDeleted ())
				continue;

			g_mapRouting.rectBound.Extend (rgn.rectBound);
		}
	}

	// NOTE: some map may contain just one point causing rectBound being empty.
	if (g_mapRouting.rectBound.x0 == g_mapRouting.rectBound.x1 && g_mapRouting.rectBound.y0 == g_mapRouting.rectBound.y1) {
		g_mapRouting.rectBound.x0 -= 1e-4f;
		g_mapRouting.rectBound.x1 += 1e-4f;
		g_mapRouting.rectBound.y0 -= 1e-4f;
		g_mapRouting.rectBound.y1 += 1e-4f;
	}

	// Set scale & position.
	SetScale (GetRectScale (g_mapRouting.rectBound));
	SetToCenter (point_t (g_mapRouting.rectBound.MeanX (), g_mapRouting.rectBound.MeanY ()));
	Refresh ();
}

///////////////////////////////////////////////////

class CRoutingTool : public CTool {
  public:
	CRoutingTool () : CTool (toolRouting), m_cRouteColor (0) {}

	virtual bool Init ();

	virtual void OnNewMap ();
	virtual void OnRemoveMap ();
	virtual void OnActivate ();
	virtual void OnDeactivate ();

	virtual void OnLDown (int _x, int _y, DWORD _dwKeys);
	virtual void OnLUp   (int _x, int _y, DWORD _dwKeys);
	virtual void OnMove  (int _x, int _y, DWORD _dwKeys);
	virtual void OnRUp   (int _x, int _y, DWORD _dwKeys);

	virtual void OnPaint (HDC _hDC, const RECT & _rect);

	virtual void OnCtxMenu (int _x, int _y);
	virtual void OnEsc ();
	virtual void OnBack ();
	virtual void OnEnter ();
	virtual bool OnCommand (WORD _wID);

  public:
	point_t m_pointLast;
	POINT m_ptLast;
	bool m_bAfterLDown;

	RECT m_rectPrev;

	CRoutingDlg m_Dlg;

	size_t m_cRouteColor;

	HCURSOR m_hcurCreateRoute;
	HMENU m_hmenuCreate;
	HMENU m_hmenuRoutes;

	void _SetCreateCursor ();
};

DECLARE_TOOL (CRoutingTool);

bool CRoutingTool::Init () {
	m_bAfterLDown = false;

	m_hcurCreateRoute = ::LoadCursor (g_hInst, MAKEINTRESOURCE (IDC_CursorCreateRoute));

	m_hmenuCreate = ::GetSubMenu (LoadMenu (IDM_POPUP), smpCreate);
	assert (m_hmenuCreate);
	m_hmenuRoutes = ::GetSubMenu (LoadMenu (IDM_POPUP), smpRoutes);
	assert (m_hmenuRoutes);

	m_Dlg.CreateModeless (g_hMapWnd);

	::GetClientRect (g_hMapWnd, & m_rectPrev);

	return true;
}

void CRoutingTool::_SetCreateCursor () {
	CTool::SetPermanentCursor (m_hcurCreateRoute);
}

void CRoutingTool::OnActivate () {
	_SetCreateCursor ();

	::SetWindowPos (m_Dlg, HWND_TOP, GetSetting ("RoutingX", (DWORD) 0), GetSetting ("RoutingY", (DWORD) 0), 0, 0, SWP_NOSIZE);
	m_Dlg.Show ();
	m_Dlg.EnableButtons ();
}

void CRoutingTool::OnDeactivate () {
	m_Dlg.Hide ();
}

void CRoutingTool::OnNewMap () {
	g_pRoute = NULL;
	m_Dlg.EnableButtons ();
}

void CRoutingTool::OnRemoveMap () {
	g_pRoute = NULL;

	{
		CCritSec::CLock lock (g_cs);
		g_mapRouting.clear ();
	}

	m_Dlg.EnableButtons ();
}

void CRoutingTool::OnLDown (int _x, int _y, DWORD _dwKeys) {
	m_bAfterLDown = true;
}

void CRoutingTool::OnLUp (int _x, int _y, DWORD _dwKeys) {
	if (! m_bAfterLDown)
		return;
	m_bAfterLDown = false;

	_SetCreateCursor ();

	ScreenToPt (_x, _y, m_pointLast);
	m_ptLast.x = _x;
	m_ptLast.y = _y;

	const bool bSingleRouteMode = ! GetSetting ("EnableMultipleRoutes", false);

	if (g_pRoute == NULL && bSingleRouteMode)
		m_Dlg.DeleteRoutes ();

	if (g_pRoute == NULL)
		g_pRoute = & _CreateRoute (m_pointLast, bSingleRouteMode ? -1 : m_cRouteColor ++);
	else
		_BuildRouteLeg (* g_pRoute, m_pointLast);

	if (bSingleRouteMode && _GetRoutePoints (g_pRoute) >= 2)
		g_pRoute = NULL;

	m_Dlg.EnableButtons ();
	Refresh ();
}

void CRoutingTool::OnMove (int _x, int _y, DWORD _dwKeys) {
	_SetCreateCursor ();

	ScreenToPt (_x, _y, m_pointLast);
	m_ptLast.x = _x;
	m_ptLast.y = _y;

	point_t pointLast;
	if (! _GetRouteLastPoint (g_pRoute, pointLast))
		return;
	const int x = XToScreen (pointLast.x);
	const int y = YToScreen (pointLast.y);

	if (x != m_ptLast.x || y != m_ptLast.y) {
		RECT rect;
		rect.left   = __min (x,  m_ptLast.x);
		rect.right  = __max (x,  m_ptLast.x);
		rect.top    = __min (y, m_ptLast.y);
		rect.bottom = __max (y, m_ptLast.y);

		RECT rect2;
		::UnionRect (& rect2, & rect, & m_rectPrev);
		::InflateRect (& rect2, c_iCrossSize + 1, c_iCrossSize + 1);

		Redraw (& rect2);

		m_rectPrev = rect;
	}
}

void CRoutingTool::OnPaint (HDC _hDC, const RECT & _rect) {
	// Draw test routes.
	if (! g_mapRouting.empty ()) {
		rect_t rectVisible;
		GetVisibleRect (rectVisible);

		g_mapRouting.Draw (_hDC, rectVisible, 0, 0, true);
	}

	const int x = m_ptLast.x;
	const int y = m_ptLast.y;

	//
	// Draw leg of route to create.
	//

	point_t pointLast;
	if (! _GetRouteLastPoint (g_pRoute, pointLast))
		return;

	POINT points [2];
	points [0].x = XToScreen (pointLast.x);
	points [0].y = YToScreen (pointLast.y);
	points [1].x = x;
	points [1].y = y;

	const HPEN hpenPolyline = ::CreatePen (PS_SOLID, GetDefaultRouteWidth (), _GetRouteColor (g_pRoute));
	const HPEN hpen = (HPEN) ::SelectObject (_hDC, hpenPolyline);

	::SetROP2 (_hDC, R2_COPYPEN);

	::Polyline (_hDC, points, 2);

	::SelectObject (_hDC, hpen);
	::DeleteObject (hpenPolyline);
}

static rgn_t * g_pRouteCtx;

void CRoutingTool::OnRUp (int _x, int _y, DWORD _dwKeys) {
	CRoutingTool::OnCtxMenu (_x, _y);
}

void CRoutingTool::OnEsc () {
	POINT p;
	::GetCursorPos (& p);
	::ScreenToClient (g_hMapWnd, & p);
	CRoutingTool::OnCtxMenu (p.x, p.y);
}

void CRoutingTool::OnCtxMenu (int _x, int _y) {
	if (g_bLoading)
		return;

	const find_info_t fi = FindRgn (g_mapRouting, _x, _y);
	if (fi.pLayer != NULL) {
		g_pRouteCtx = & * fi.iRgn;
		if (g_pRouteCtx == g_pRoute) {
			// NOTE: don't delete or reverse the route under creation.
			g_pRouteCtx = NULL;
			return;
		}
		PopupMenu (m_hmenuRoutes);
		return;
	} else
		g_pRouteCtx = NULL;

	const size_t cPoints = _GetRoutePoints (g_pRoute);
	const bool bCanEnd = cPoints >= 2;

	::EnableMenuItem (m_hmenuCreate, ID_CREATE_REMOVE_LAST_NODE, (cPoints > 0 ? MF_ENABLED : MF_GRAYED) | MF_BYCOMMAND);
	::EnableMenuItem (m_hmenuCreate, ID_CREATE_END,              (bCanEnd     ? MF_ENABLED : MF_GRAYED) | MF_BYCOMMAND);
	::EnableMenuItem (m_hmenuCreate, ID_CREATE_CONTINUE,         (cPoints > 0 ? MF_ENABLED : MF_GRAYED) | MF_BYCOMMAND);
//	::EnableMenuItem (m_hmenuCreate, ID_CREATE_CANCEL,           (cPoints > 0 ? MF_ENABLED : MF_GRAYED) | MF_BYCOMMAND);

	PopupMenu (m_hmenuCreate);
}

void CRoutingTool::OnBack () {
	CRoutingTool::OnCommand (ID_CREATE_REMOVE_LAST_NODE);
}

void CRoutingTool::OnEnter () {
	CRoutingTool::OnCommand (ID_CREATE_END);
}

bool CRoutingTool::OnCommand (WORD _wID) {
	switch (_wID) {
		case ID_CREATE_REMOVE_LAST_NODE:
			if (g_pRoute) {
				if (_RouteRemoveLastLeg (g_pRoute))
					g_pRoute = NULL;

				m_Dlg.EnableButtons ();
				Refresh ();
			}
			return true;

		case ID_CREATE_END: {
			if (g_pRoute) {
				g_pRoute = NULL;

				m_Dlg.EnableButtons ();
				Refresh ();
			}

			return true;
		}

		case ID_CREATE_CONTINUE:
			return true;

		case ID_CREATE_CANCEL:
			if (g_pRoute) {
				g_pRoute->Delete (g_mapRouting);
				_CollectGarbage ();
				Refresh ();
			}
			return true;

		case ID_ROUTE_DELETE:
			if (g_pRouteCtx) {
				g_pRouteCtx->Delete (g_mapRouting);
				_CollectGarbage ();
				Refresh ();
			}
			return true;

		case ID_ROUTE_REVERSE:
			if (g_pRouteCtx) {
				_ReverseRoute (* g_pRouteCtx);
				Refresh ();
			}
			return true;

		case ID_ROUTE_REBUILD:
			if (g_pRouteCtx) {
				_RebuildRoute (* g_pRouteCtx);
				Refresh ();
			}
			return true;
	}

	return false;
}
